package cn.juni.controller;

import cn.juni.pojo.SysLog;
import cn.juni.service.ISysLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.UUID;

/**
 * @author Juni
 * @date 2021/3/3
 */
@Component
//@Aspect注解表示该类为切面类，需要在每一个控制层的方法执行之前和执行之后要获取到对应的用户的信息以及其操作信息
@Aspect
public class LogAop {

    /**
     * 访问时间（访问开始的时间）
     */
    private Date visitTime;

    /**
     * 为了获取类相关信息
     */
    private Class clazz;

    /**
     * 为了获取所执行的方法的具体信息（方法名、方法的参数等）
     */
    private Method method;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    ISysLogService sysLogService;

    /**
     * 1.获取方法开始执行的时间
     * 2.获取执行的方法的信息及类信息
     * @param joinPoint
     * @throws NoSuchMethodException
     */
    @Before("execution(* cn.juni.controller.*.*(..))")
    public void before(JoinPoint joinPoint) throws NoSuchMethodException {
        //1.获取方法执行的开始时间
        visitTime = new Date();
        //2.获取类信息-找到访问的类
        clazz = joinPoint.getTarget().getClass();
        //2.1 获取所访问的方法名
        String methodName = joinPoint.getSignature().getName();
        //2.2 获取方法中的参数
        Object[] argsArr = joinPoint.getArgs();
        //3.获取具体的Method对象（稍后需要将对应的信息存入到数据库中）
        if(argsArr == null || argsArr.length == 0){//获取无参数的方法
            method = clazz.getMethod(methodName);
        } else{
            //3.1 获取带有参数的方法需要指定参数的类型
            Class[] argsClass = new Class[argsArr.length];
            //3.2 遍历参数数组，对class类型的数组进行赋值
            for(int i=0;i<argsArr.length;i++){
                //3.2.1 控制层的方法中带有Model参数，该Model是SpringMVC中的数据类型，需要通过MVC提供的Handler映射器进行映射
                //(简单说法：方法中有Model参数，该参数需要通过BindingAwareModelMap去进行匹配)
                if(argsArr[i].getClass() == BindingAwareModelMap.class){
                    argsClass[i] = Model.class;
                }else{
                    argsClass[i] = argsArr[i].getClass();
                }
            }
            method = clazz.getMethod(methodName,argsClass);
        }
    }

    /**
     * 目标：获取到具体的请求地址
     * 1.类上有地址
     * 2.方法上有地址
     * 3.地址是写在@RequestMapping注解上的
     */
    @After("execution(* cn.juni.controller.*.*(..))")
    public void after(){
        //1.获取执行时长（方法结束时时间-开始时间）
        long time = new Date().getTime() - visitTime.getTime();
        //2.获取请求地址信息（request）
        if(clazz != null && method != null && clazz != SysLogController.class){//除了日志管理的模块之外的所有其他模块的操作都应该被记录
            //2.1 获取类上的注解所对应的class文件
            RequestMapping classRequestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
            if(classRequestMapping != null){
                //2.1.1 因为requestMapping中可以同时给定多个地址，所以返回的是一个数组
                String[] classValues = classRequestMapping.value();
                //2.2 获取方法上的请求地址
                RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
                String[] methodValues = methodRequestMapping.value();
                //2.3获取到类上请求地址+方法上的请求地址，拼接后就是具体的url
                String url = classValues[0]+methodValues[0];
                //3. 获取IP地址
                String ip = httpServletRequest.getRemoteAddr();
                //4. 获取当前登录的用户信息
                SecurityContext securityContext = SecurityContextHolder.getContext();
                User user = (User) securityContext.getAuthentication().getPrincipal();
                String userName = user.getUsername();

                //5.将上述所有的信息封装到SysLog类中
                SysLog sysLog = new SysLog();
                sysLog.setId(UUID.randomUUID().toString().replace("-",""));
                sysLog.setVisitTime(visitTime);
                sysLog.setIp(ip);
                sysLog.setUrl(url);
                sysLog.setExecutionTime(time);
                sysLog.setUsername(userName);
                sysLog.setMethod("[类名]"+clazz.getName()+"[方法名]"+method.getName());

                //6.调用service层添加数据
                sysLogService.insertLog(sysLog);
            }
        }
    }
}
